package com.spring.cloud.alibabaconsumer.service.impl;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.spring.cloud.alibaba.commons.pojo.OrderInfo;
import com.spring.cloud.alibaba.commons.pojo.ResponseVo;
import com.spring.cloud.alibaba.commons.pojo.StockInfo;
import com.spring.cloud.alibabaconsumer.mapper.OrderMapper;
import com.spring.cloud.alibabaconsumer.service.OrderService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import redis.clients.jedis.Jedis;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, OrderInfo> implements OrderService {

    private final static Logger logger = LoggerFactory.getLogger(OrderServiceImpl.class);

    private final static SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss.SSS");

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RedisConnectionFactory redisConnectionFactory;

    @Resource(name="customTaskWorkPoolExecutor")
    private ThreadPoolExecutor threadPoolExecutor;

    @Value("${stock.service.query-stock-url}")
    private String queryGoodStockServiceUrl;

    @Value("${stock.service.update-count-url}")
    private String updateStockCountUrl;

    @Override
    public ResponseVo createOrder(OrderInfo orderEntity, Integer flag) {
        ResponseVo responseVo;
        if (flag == 1 || flag == 2) {
            responseVo = setNxLock(orderEntity, flag);
        } else {
            responseVo = redisTemplateLock(orderEntity);
        }
        return responseVo;
    }

    private void completeOrderInfo(OrderInfo orderInfo) {
        if (orderInfo.getUserId() == null) {
            orderInfo.setUserId(1L);
        }
        String orderNo = sdf.format(new Date(System.currentTimeMillis()));
        logger.info("orderNo={}", orderNo);
        orderInfo.setOrderNo(orderNo);
        Date now = new Date(System.currentTimeMillis());
        orderInfo.setCreatedBy("system");
        orderInfo.setCreatedDate(now);
        orderInfo.setLastUpdatedBy("system");
        orderInfo.setLastUpdatedDate(now);
    }


    private ResponseVo setNxLock(OrderInfo orderEntity, Integer flag) {
        String goodCode = orderEntity.getGoodCode();
        Jedis jedis = (Jedis) redisConnectionFactory.getConnection().getNativeConnection();
        // 查库存时加上分布式锁
        String lockKey = "lock_" + goodCode;
        long currentTime = System.currentTimeMillis();
        Long lockResult = jedis.setnx(lockKey, String.valueOf(currentTime));
        if (lockResult == 1) {
            // 设置锁失效时间5s
            try {
                 jedis.expire(lockKey, 5);
                 logger.info("get distribute lock success, lockKey={}", lockKey);
                 return queryStockAndInsertOrder(orderEntity);
             } catch (Exception e) {
                  logger.error("", e);
                  return ResponseVo.error(e.getMessage());
            } finally {
                delLockByExecuteJedisCommand(jedis, lockKey, currentTime, flag);
            }
        } else {
            logger.warn("get redis lock failed, stop to order");
            return ResponseVo.error("请稍后再下单，其他客户正在对同一商品下单");
        }
    }

    /**
     * 查询库存并保存订单
     * @param orderEntity
     * @return
     */
    private ResponseVo queryStockAndInsertOrder(OrderInfo orderEntity) {
        String goodCode = orderEntity.getGoodCode();
        String requestUrl = queryGoodStockServiceUrl + "?goodCode={goodCode}";
        Map<String, Object> paramMap = new HashMap<>(1);
        paramMap.put("goodCode", goodCode);
        JSONObject jsonResponse = restTemplate.getForObject(requestUrl, JSONObject.class, paramMap);
        logger.info("queryResponse={}", JSONUtil.toJsonStr(jsonResponse));
        if (jsonResponse == null) {
              return ResponseVo.error("远程调用库存服务失败");
        }
        int status = jsonResponse.getInt("status");
        if (status != 200) {
            return ResponseVo.error(status, jsonResponse.getStr("message"));
        }
        StockInfo stockInfo = jsonResponse.get("data", StockInfo.class);
        if (stockInfo.getCount() <= orderEntity.getGoodCount()) {
            return ResponseVo.error("商品库存不足");
        }
        completeOrderInfo(orderEntity);
        int insertCount = this.baseMapper.insert(orderEntity);
        logger.info("insertCount={}", insertCount);
        // 异步减库存
        asyncDecreaseStock(stockInfo, orderEntity.getGoodCount());
        return ResponseVo.success(orderEntity);
    }

    private ResponseVo redisTemplateLock(OrderInfo orderEntity) {
        String goodCode = orderEntity.getGoodCode();
        String lockKey = "lock_" + goodCode;
        Long value = System.currentTimeMillis();
        // ValueOperation#setIfAbsent(key, value)等同与jedis.setNx(key,value)方法，都可以实现redis不存在key值时的添加缓存
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, String.valueOf(value));
        if (flag) {
            // 枷锁成功,执行查库存操作
            try {
                stringRedisTemplate.expire(lockKey, 5, TimeUnit.SECONDS);
                logger.info("get distribute lock success, lockKey={}", lockKey);
                return queryStockAndInsertOrder(orderEntity);
            } catch (Exception e) {
                logger.error("", e);
                return ResponseVo.error(e.getMessage());
            } finally {
                String script = "local value = redis.call('GET', KEYS[1])\n" +
                            "if value == ARGV[1] then \n" +
                            "  redis.call('DEL', KEYS[1])" +
                            "return 1 \n" +
                            "end " +
                            "return 0 \n" ;
                RedisScript<Long> redisScript = RedisScript.of(script, Long.class);
                List<String> keys = new ArrayList<>(1);
                keys.add(lockKey);
                Long count =  stringRedisTemplate.execute(redisScript, keys, String.valueOf(value));
                if (count == 1) {
                    logger.info("release redis lock success");
                } else {
                    logger.warn("release redis lock failed");
                }

            }
        } else {
            logger.warn("get redis lock failed, stop to order");
            return ResponseVo.error("请稍后再下单，其他客户正在对同一商品下单");
        }

    }


    private void delLockByExecuteJedisCommand(Jedis jedis, String lockKey, Long currentTime, Integer flag) {
        if (flag==1) {
            String value =  jedis.get(lockKey);
            if (value !=null && Long.parseLong(value) == currentTime) {
                jedis.del(lockKey);
                logger.info("release redis lock, lockKey={}",lockKey);
            }
        } else if (flag == 2) {
            delLockByJedisExecuteLuaScript(jedis, lockKey, currentTime);
        }
    }

    private void delLockByJedisExecuteLuaScript(Jedis jedis, String lockKey, Long currentTime) {
        String script = "local value = redis.call('GET', KEYS[1])\n" +
                "if value == ARGV[1] then \n" +
                "  redis.call('DEL', KEYS[1])" +
                "return 1 \n" +
                "end " +
                "return 0 \n" ;
        List<String> keys = new ArrayList<>(1);
        keys.add(lockKey);
        List<String> args = new ArrayList<>(1);
        args.add(String.valueOf(currentTime));
        Long count = (Long) jedis.eval(script, keys, args);
        if (count == 1) {
            logger.info("release redis lock success");
        } else {
            logger.warn("release redis lock failed");
        }
    }

    /**
     * 异步减库存
     * @param stockInfo
     * @param orderCount
     */
    private void asyncDecreaseStock(StockInfo stockInfo, int orderCount) {
        threadPoolExecutor.execute(() -> {
            // 减库存
            int remainCount = stockInfo.getCount() - orderCount;
            stockInfo.setCount(remainCount);
            stockInfo.setLastUpdatedBy("system");
            stockInfo.setLastUpdatedDate(new Date(System.currentTimeMillis()));
            ResponseVo updateResponse = restTemplate.postForObject(updateStockCountUrl, stockInfo, ResponseVo.class);
            logger.info("updateResponse={}", JSONUtil.toJsonStr(updateResponse));
            if (updateResponse.getStatus() == 200) {
                logger.info("update stock count success");
            } else {
                logger.warn("update stock count failed, stockInfo={}, remainCount={}", stockInfo, remainCount);
            }
        });
    }

}
